#include "wx/wxprec.h"
#if wxUSE_STREAMS && wxUSE_GIF

#ifndef WX_PRECOMP
#include "wx/palette.h"
#include "wx/log.h"
#include "wx/intl.h"
#endif

#include <stdlib.h>
#include <string.h>
#include "wx/gifdecod.h"

class GIFImage {
  public:
    GIFImage();
    unsigned int w;                 // width
    unsigned int h;                 // height
    unsigned int left;              // x coord (in logical screen)
    unsigned int top;               // y coord (in logical screen)
    int transparent;                // transparent color index (-1 = none)
    wxAnimationDisposal disposal;   // disposal method
    long delay;                     // delay in ms (-1 = unused)
    unsigned char *p;               // bitmap
    unsigned char *pal;             // palette
    unsigned int ncolours;          // number of colours

    DECLARE_NO_COPY_CLASS( GIFImage )
};

GIFImage::GIFImage() {
  w = 0;
  h = 0;
  left = 0;
  top = 0;
  transparent = 0;
  disposal = wxANIM_DONOTREMOVE;
  delay = -1;
  p = ( unsigned char * ) NULL;
  pal = ( unsigned char * ) NULL;
  ncolours = 0;
}

wxGIFDecoder::wxGIFDecoder() {
}

wxGIFDecoder::~wxGIFDecoder() {
  Destroy();
}

void wxGIFDecoder::Destroy() {
  wxASSERT( m_nFrames == m_frames.GetCount() );
  for( unsigned int i = 0; i < m_nFrames; i++ ) {
    GIFImage *f = ( GIFImage* )m_frames[i];
    free( f->p );
    free( f->pal );
    delete f;
  }
  m_frames.Clear();
  m_nFrames = 0;
}

bool wxGIFDecoder::ConvertToImage( unsigned int frame, wxImage *image ) const {
  unsigned char *src, *dst, *pal;
  unsigned long i;
  int      transparent;
  image->Destroy();
  wxSize sz = GetFrameSize( frame );
  image->Create( sz.GetWidth(), sz.GetHeight() );
  if( !image->Ok() ) {
    return false;
  }
  pal = GetPalette( frame );
  src = GetData( frame );
  dst = image->GetData();
  transparent = GetTransparentColourIndex( frame );
  if( transparent != -1 ) {
    for( i = 0; i < GetNcolours( frame ); i++ ) {
      if( ( pal[3 * i + 0] == 255 ) && ( pal[3 * i + 1] == 0 ) && ( pal[3 * i + 2] == 255 ) ) {
        pal[3 * i + 2] = 254;
      }
    }
    pal[3 * transparent + 0] = 255, pal[3 * transparent + 1] = 0, pal[3 * transparent + 2] = 255;
    image->SetMaskColour( 255, 0, 255 );
  } else {
    image->SetMask( false );
  }
  #if wxUSE_PALETTE
  unsigned char r[256];
  unsigned char g[256];
  unsigned char b[256];
  for( i = 0; i < 256; i++ ) {
    r[i] = pal[3 * i + 0];
    g[i] = pal[3 * i + 1];
    b[i] = pal[3 * i + 2];
  }
  image->SetPalette( wxPalette( GetNcolours( frame ), r, g, b ) );
  #endif
  unsigned long npixel = sz.GetWidth() * sz.GetHeight();
  for( i = 0; i < npixel; i++, src++ ) {
    *( dst++ ) = pal[3 * ( *src ) + 0];
    *( dst++ ) = pal[3 * ( *src ) + 1];
    *( dst++ ) = pal[3 * ( *src ) + 2];
  }
  return true;
}

#define GetFrame(n)     ((GIFImage*)m_frames[n])

wxSize wxGIFDecoder::GetFrameSize( unsigned int frame ) const {
  return wxSize( GetFrame( frame )->w, GetFrame( frame )->h );
}

wxPoint wxGIFDecoder::GetFramePosition( unsigned int frame ) const {
  return wxPoint( GetFrame( frame )->left, GetFrame( frame )->top );
}

wxAnimationDisposal wxGIFDecoder::GetDisposalMethod( unsigned int frame ) const {
  return GetFrame( frame )->disposal;
}

long wxGIFDecoder::GetDelay( unsigned int frame ) const {
  return GetFrame( frame )->delay;
}

wxColour wxGIFDecoder::GetTransparentColour( unsigned int frame ) const {
  unsigned char *pal = GetFrame( frame )->pal;
  int n = GetFrame( frame )->transparent;
  if( n == -1 ) {
    return wxNullColour;
  }
  return wxColour( pal[n * 3 + 0], pal[n * 3 + 1], pal[n * 3 + 2] );
}

unsigned char* wxGIFDecoder::GetData( unsigned int frame ) const {
  return ( GetFrame( frame )->p );
}
unsigned char* wxGIFDecoder::GetPalette( unsigned int frame ) const {
  return ( GetFrame( frame )->pal );
}
unsigned int wxGIFDecoder::GetNcolours( unsigned int frame ) const {
  return ( GetFrame( frame )->ncolours );
}
int wxGIFDecoder::GetTransparentColourIndex( unsigned int frame ) const {
  return ( GetFrame( frame )->transparent );
}

int wxGIFDecoder::getcode( wxInputStream& stream, int bits, int ab_fin ) {
  unsigned int mask;
  unsigned int code;
  mask = ( 1 << bits ) - 1;
  code = ( m_lastbyte >> ( 8 - m_restbits ) ) & mask;
  while( bits > m_restbits ) {
    if( m_restbyte == 0 ) {
      m_restbyte = ( unsigned char )stream.GetC();
      if( m_restbyte == 0 ) {
        code = ab_fin;
        break;
      }
      stream.Read( ( void * ) m_buffer, m_restbyte );
      if( stream.LastRead() != m_restbyte ) {
        code = ab_fin;
        return code;
      }
      m_bufp = m_buffer;
    }
    m_lastbyte = ( unsigned char )( *m_bufp++ );
    mask = ( 1 << ( bits - m_restbits ) ) - 1;
    code = code + ( ( m_lastbyte & mask ) << m_restbits );
    m_restbyte--;
    m_restbits = m_restbits + 8;
  }
  m_restbits = ( m_restbits - bits );
  return code;
}

wxGIFErrorCode wxGIFDecoder::dgif( wxInputStream& stream, GIFImage *img, int interl, int bits ) {
  static const int allocSize = 4096 + 1;
  int *ab_prefix = new int[allocSize];
  if( ab_prefix == NULL ) {
    return wxGIF_MEMERR;
  }
  int *ab_tail = new int[allocSize];
  if( ab_tail == NULL ) {
    delete[] ab_prefix;
    return wxGIF_MEMERR;
  }
  int *stack = new int[allocSize];
  if( stack == NULL ) {
    delete[] ab_prefix;
    delete[] ab_tail;
    return wxGIF_MEMERR;
  }
  int ab_clr;
  int ab_fin;
  int ab_bits;
  int ab_free;
  int ab_max;
  int pass;
  int pos;
  unsigned int x, y;
  int code, readcode, lastcode, abcabca;
  ab_clr = ( 1 << bits );
  ab_fin = ( 1 << bits ) + 1;
  ab_bits  = bits + 1;
  ab_free  = ( 1 << bits ) + 2;
  ab_max   = ( 1 << ab_bits ) - 1;
  lastcode = -1;
  abcabca  = -1;
  pass     = 1;
  pos = x = y = 0;
  m_restbits = 0;
  m_restbyte = 0;
  m_lastbyte = 0;
  do {
    readcode = code = getcode( stream, ab_bits, ab_fin );
    if( code == ab_fin ) {
      break;
    }
    if( code == ab_clr ) {
      ab_bits  = bits + 1;
      ab_free  = ( 1 << bits ) + 2;
      ab_max   = ( 1 << ab_bits ) - 1;
      lastcode = -1;
      abcabca  = -1;
      continue;
    }
    if( code >= ab_free ) {
      code = lastcode;
      stack[pos++] = abcabca;
    }
    while( code > ab_clr ) {
      stack[pos++] = ab_tail[code];
      code = ab_prefix[code];
      if( pos >= allocSize ) {
        delete[] ab_prefix;
        delete[] ab_tail;
        delete[] stack;
        return wxGIF_INVFORMAT;
      }
    }
    if( pos >= allocSize ) {
      delete[] ab_prefix;
      delete[] ab_tail;
      delete[] stack;
      return wxGIF_INVFORMAT;
    }
    stack[pos] = code;
    abcabca    = code;
    if( lastcode != -1 ) {
      if( ab_free > ab_max ) {
        delete[] ab_prefix;
        delete[] ab_tail;
        delete[] stack;
        return wxGIF_INVFORMAT;
      }
      wxASSERT( ab_free < allocSize );
      ab_prefix[ab_free] = lastcode;
      ab_tail[ab_free]   = code;
      ab_free++;
      if( ( ab_free > ab_max ) && ( ab_bits < 12 ) ) {
        ab_bits++;
        ab_max = ( 1 << ab_bits ) - 1;
      }
    }
    while( pos >= 0 ) {
      ( img->p )[x + ( y * ( img->w ) )] = ( char ) stack[pos];
      pos--;
      if( ++x >= ( img->w ) ) {
        x = 0;
        if( interl ) {
          switch( pass ) {
            case 1:
              y += 8;
              break;
            case 2:
              y += 8;
              break;
            case 3:
              y += 4;
              break;
            case 4:
              y += 2;
              break;
          }
          while( y >= ( img->h ) ) {
            switch( ++pass ) {
              case 2:
                y = 4;
                break;
              case 3:
                y = 2;
                break;
              case 4:
                y = 1;
                break;
              default:
                y = 0;
                pos = -1;
                code = ab_fin;
                break;
            }
          }
        } else {
          y++;
          if( y >= img->h ) {
            code = ab_fin;
            break;
          }
        }
      }
    }
    pos = 0;
    lastcode = readcode;
  } while( code != ab_fin );
  delete [] ab_prefix ;
  delete [] ab_tail ;
  delete [] stack ;
  return wxGIF_OK;
}

bool wxGIFDecoder::CanRead( wxInputStream &stream ) const {
  unsigned char buf[3];
  if( !stream.Read( buf, WXSIZEOF( buf ) ) ) {
    return false;
  }
  stream.SeekI( -( wxFileOffset )WXSIZEOF( buf ), wxFromCurrent );
  return memcmp( buf, "GIF", WXSIZEOF( buf ) ) == 0;
}

wxGIFErrorCode wxGIFDecoder::LoadGIF( wxInputStream& stream ) {
  unsigned int  global_ncolors = 0;
  int bits, interl, i;
  wxAnimationDisposal disposal;
  long size;
  long delay;
  unsigned char type = 0;
  unsigned char pal[768];
  unsigned char buf[16];
  bool anim = true;
  if( !CanRead( stream ) ) {
    return wxGIF_INVFORMAT;
  }
  static const unsigned int headerSize = ( 3 + 3 );
  stream.Read( buf, headerSize );
  if( stream.LastRead() != headerSize ) {
    return wxGIF_INVFORMAT;
  }
  if( memcmp( buf + 3, "89a", 3 ) < 0 ) {
    anim = false;
  }
  static const unsigned int lsdbSize = ( 2 + 2 + 1 + 1 + 1 );
  stream.Read( buf, lsdbSize );
  if( stream.LastRead() != lsdbSize ) {
    return wxGIF_INVFORMAT;
  }
  m_szAnimation.SetWidth( buf[0] + 256 * buf[1] );
  m_szAnimation.SetHeight( buf[2] + 256 * buf[3] );
  if( anim && ( ( m_szAnimation.GetWidth() == 0 ) || ( m_szAnimation.GetHeight() == 0 ) ) ) {
    return wxGIF_INVFORMAT;
  }
  if( ( buf[4] & 0x80 ) == 0x80 ) {
    int backgroundColIndex = buf[5];
    global_ncolors = 2 << ( buf[4] & 0x07 );
    unsigned int numBytes = 3 * global_ncolors;
    stream.Read( pal, numBytes );
    if( stream.LastRead() != numBytes ) {
      return wxGIF_INVFORMAT;
    }
    m_background.Set( pal[backgroundColIndex * 3 + 0], pal[backgroundColIndex * 3 + 1], pal[backgroundColIndex * 3 + 2] );
  }
  int transparent = -1;
  disposal = wxANIM_UNSPECIFIED;
  delay = -1;
  bool done = false;
  while( !done ) {
    type = ( unsigned char )stream.GetC();
    if( stream.Eof() ) {
      break;
    }
    if( type == 0x3B ) {
      done = true;
    } else if( type == 0x21 ) {
      if( ( ( unsigned char )stream.GetC() ) == 0xF9 ) {
        static const unsigned int gceSize = 6;
        stream.Read( buf, gceSize );
        if( stream.LastRead() != gceSize ) {
          Destroy();
          return wxGIF_INVFORMAT;
        }
        delay = 10 * ( buf[2] + 256 * buf[3] );
        transparent = buf[1] & 0x01 ? buf[4] : -1;
        disposal = ( wxAnimationDisposal )( ( ( buf[1] & 0x1C ) >> 2 ) - 1 );
      } else {
        while( ( i = ( unsigned char )stream.GetC() ) != 0 ) {
          if( stream.Eof() || ( stream.LastRead() == 0 ) ) {
            done = true;
            break;
          }
          stream.SeekI( i, wxFromCurrent );
        }
      }
    } else if( type == 0x2C ) {
      GIFImage *pimg = new GIFImage();
      if( pimg == NULL ) {
        Destroy();
        return wxGIF_MEMERR;
      }
      static const unsigned int idbSize = ( 2 + 2 + 2 + 2 + 1 );
      stream.Read( buf, idbSize );
      if( stream.LastRead() != idbSize ) {
        Destroy();
        return wxGIF_INVFORMAT;
      }
      pimg->left = buf[0] + 256 * buf[1];
      pimg->top = buf[2] + 256 * buf[3];
      pimg->w = buf[4] + 256 * buf[5];
      pimg->h = buf[6] + 256 * buf[7];
      if( anim ) {
        if( m_nFrames == 0 ) {
          if( pimg->w > ( unsigned )m_szAnimation.x ) {
            m_szAnimation.x = pimg->w;
          }
          if( pimg->h > ( unsigned )m_szAnimation.y ) {
            m_szAnimation.y = pimg->h;
          }
        } else {
          if( ( !pimg->w || pimg->w > ( unsigned )m_szAnimation.x ) ||
              ( !pimg->h || pimg->h > ( unsigned )m_szAnimation.y ) ) {
            wxLogError( _( "Incorrect GIF frame size (%u, %d) for the frame #%u" ), pimg->w, pimg->h, m_nFrames );
            Destroy();
            return wxGIF_INVFORMAT;
          }
        }
      }
      interl = ( ( buf[8] & 0x40 ) ? 1 : 0 );
      size = pimg->w * pimg->h;
      pimg->transparent = transparent;
      pimg->disposal = disposal;
      pimg->delay = delay;
      pimg->p   = ( unsigned char * ) malloc( ( unsigned int )size );
      pimg->pal = ( unsigned char * ) malloc( 768 );
      if( ( !pimg->p ) || ( !pimg->pal ) ) {
        Destroy();
        return wxGIF_MEMERR;
      }
      if( ( buf[8] & 0x80 ) == 0x80 ) {
        unsigned int local_ncolors = 2 << ( buf[8] & 0x07 );
        unsigned int numBytes = 3 * local_ncolors;
        stream.Read( pimg->pal, numBytes );
        pimg->ncolours = local_ncolors;
        if( stream.LastRead() != numBytes ) {
          Destroy();
          return wxGIF_INVFORMAT;
        }
      } else {
        memcpy( pimg->pal, pal, 768 );
        pimg->ncolours = global_ncolors;
      }
      bits = ( unsigned char )stream.GetC();
      if( bits == 0 ) {
        Destroy();
        return wxGIF_INVFORMAT;
      }
      wxGIFErrorCode result = dgif( stream, pimg, interl, bits );
      if( result != wxGIF_OK ) {
        Destroy();
        return result;
      }
      m_frames.Add( ( void* )pimg );
      m_nFrames++;
      if( !anim ) {
        done = true;
      }
    }
  }
  if( m_nFrames <= 0 ) {
    Destroy();
    return wxGIF_INVFORMAT;
  }
  while( type != 0x3B ) {
    if( !stream.IsOk() ) {
      return wxGIF_TRUNCATED;
    }
    type = ( unsigned char )stream.GetC();
    if( type == 0x21 ) {
      ( void ) stream.GetC();
      while( ( i = ( unsigned char )stream.GetC() ) != 0 ) {
        if( stream.Eof() || ( stream.LastRead() == 0 ) ) {
          Destroy();
          return wxGIF_INVFORMAT;
        }
        stream.SeekI( i, wxFromCurrent );
      }
    } else if( type == 0x2C ) {
      static const unsigned int idbSize = ( 2 + 2 + 2 + 2 + 1 );
      stream.Read( buf, idbSize );
      if( stream.LastRead() != idbSize ) {
        Destroy();
        return wxGIF_INVFORMAT;
      }
      if( ( buf[8] & 0x80 ) == 0x80 ) {
        unsigned int local_ncolors = 2 << ( buf[8] & 0x07 );
        wxFileOffset numBytes = 3 * local_ncolors;
        stream.SeekI( numBytes, wxFromCurrent );
      }
      ( void ) stream.GetC();
      if( stream.Eof() || ( stream.LastRead() == 0 ) ) {
        Destroy();
        return wxGIF_INVFORMAT;
      }
      while( ( i = ( unsigned char )stream.GetC() ) != 0 ) {
        if( stream.Eof() || ( stream.LastRead() == 0 ) ) {
          Destroy();
          return wxGIF_INVFORMAT;
        }
        stream.SeekI( i, wxFromCurrent );
      }
    } else if( ( type != 0x3B ) && ( type != 00 ) ) {
      return wxGIF_TRUNCATED;
    }
  }
  return wxGIF_OK;
}

#endif
